package com.gitee.ed.pattern.singleton;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 对象实例化构建器
 *
 * @author hongda.li
 */
public class BeanCreator {
    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(BeanCreator.class);
    /**
     * 当前需要实例化的对象类型
     */
    private final Class<?> beanType;

    public BeanCreator(Class<?> beanType) {
        this.beanType = beanType;
    }

    public Object create() {
        return this.create(beanType, new LinkedHashSet<>());
    }

    private Object create(Class<?> toInstantiated, Set<Class<?>> remember) {
        // 二次判断，防止在依赖注册的过程中，已注册当前Bean
        Object exists = BeanContainer.findFirst(toInstantiated);
        if (exists != null) {
            return exists;
        }

        LOGGER.debug("Prepared to instantiate bean - {}", toInstantiated.getName());

        // 防止循环依赖
        if (remember.contains(toInstantiated)) {
            throw new RuntimeException("Cyclic dependency found - " + ObjectUtil.toString(remember));
        } else {
            remember.add(toInstantiated);
        }

        // 获取当前对象的构造器
        Constructor<?>[] constructors = ReflectUtil.getConstructors(toInstantiated);
        Assert.isTrue(constructors.length == 1, "Too many constructor in class - {}", toInstantiated.getName());
        Constructor<?> constructor = constructors[0];

        Object bean;
        if (constructor.getParameterCount() == 0) {
            LOGGER.debug("Use none parameter constructor to instantiate");
            bean = ReflectUtil.newInstance(toInstantiated);
        } else {
            bean = ReflectUtil.newInstance(toInstantiated, this.fetchDependencies(constructor.getParameters(), remember).toArray());
        }

        // 重新注册已实例化的Bean
        BeanContainer.register(toInstantiated, bean);

        return bean;
    }

    private List<Object> fetchDependencies(Parameter[] parameters, Set<Class<?>> remember) {
        List<Object> valueList = new ArrayList<>(parameters.length);

        for (Parameter parameter : parameters) {
            Class<?> type = parameter.getType();
            boolean aggregation = List.class.equals(type);
            if (aggregation) {
                Class<?> elementType = (Class<?>) TypeUtil.getTypeArgument(parameter.getParameterizedType());
                LOGGER.debug("Try to fetch all of the dependency - {}", elementType.getName());
                valueList.add(BeanContainer.getBeanTypes()
                        .stream()
                        .filter(elementType::isAssignableFrom)
                        .map(clz -> this.create(clz, remember))
                        .filter(Objects::nonNull)
                        .toList());
            } else {
                LOGGER.debug("Try to fetch one of the dependency - {}", type);
                Assert.isTrue(BeanContainer.getBeanTypes().contains(type), "Not found of bean - {}", type.getName());
                Object find = BeanContainer.findFirst(type);
                if (find == null) {
                    valueList.add(this.create(type, remember));
                } else {
                    valueList.add(find);
                }
            }
        }

        return valueList;
    }
}
